/*
 * sutil.h
 *
 *  Created on: 2012-4-3
 *      Author: Administrator
 */

#ifndef SUTIL_H_
#define SUTIL_H_

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <dlfcn.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#define MAX_CFG_KEYLEN	65
#define MAX_CFG_VALLEN	256
#define MAX_CFG_LINE	257
#define MAX_INIPAIR	128
#define MAX_PATHLEN	255

#define MAX_BUF_SIZE	1024

#define ERR_LOG_FNAME	"monitor.log"
#define DEF_INI_PATH	"./smarter.ini"

#ifdef __cplusplus
extern "C" {
#endif


/**
 * @brief Structure that represents a single key-value pair inside
 * the ini file.
 */
struct IniPair {
	char akey	[MAX_CFG_KEYLEN];
	char avalue	[MAX_CFG_VALLEN];
};


/**
 * @brief Structure that represents one or more ini files.
 */
struct IniFile {
	FILE *pfile;
	struct IniPair aini_pair[MAX_INIPAIR];
	char aini_path[MAX_PATHLEN];
	int count;
};


/**
 * @brief The function will read the ini file into the IniFile structure.
 *
 * @param _pFile Path to the .ini file.
 * @param _pIniFile Pointer to the IniFile structure.
 *
 * @return Number of IniPairs that were inferred.
 */
int api_fetchIni(const char *_pFile, struct IniFile *_pIniFile) {

	if(_pIniFile == NULL) {
		time_t t=time(NULL);
		char *tstr = asctime(localtime(&t));
		tstr[strlen(tstr)-1] = '\0';
		FILE * errorLog = fopen(ERR_LOG_FNAME,"a+");
		fprintf(errorLog, "[%s][%s, %s, %d]Invalid struct IniFile pointer. Should not be NULL.\n", tstr, __FILE__, __FUNCTION__, __LINE__);
		fclose(errorLog);
		return -1;
	}

	if( _pIniFile->count >= MAX_INIPAIR) {
		time_t t=time(NULL);
		char *tstr = asctime(localtime(&t));
		tstr[strlen(tstr)-1] = '\0';
		FILE * errorLog = fopen(ERR_LOG_FNAME,"a+");
		fprintf(errorLog, "[%s][%s, %s, %d]struct IniFile.aIniPair is full. Should be less than %d.\n", tstr, __FILE__, __FUNCTION__, __LINE__, MAX_INIPAIR);
		fclose(errorLog);
		return -1;
	}

	if(_pFile != NULL) {
		if(strlen(_pFile)>MAX_PATHLEN-1) {
			time_t t=time(NULL);
			char *tstr = asctime(localtime(&t));
			tstr[strlen(tstr)-1] = '\0';
			FILE * errorLog = fopen(ERR_LOG_FNAME,"a+");
			fprintf(errorLog, "[%s][%s, %s, %d].ini File path too long. Should be less than %d.\n", tstr,__FILE__, __FUNCTION__, __LINE__, MAX_PATHLEN);
			fclose(errorLog);
			return -1;
		}
		memcpy(_pIniFile->aini_path, _pFile, strlen( _pFile)+1);
	} else {
		if( strlen(_pIniFile->aini_path) == 0) {
			time_t t=time(NULL);
			char *tstr = asctime(localtime(&t));
			tstr[strlen(tstr)-1] = '\0';
			FILE *errorLog = fopen(ERR_LOG_FNAME, "a+");
			fprintf(errorLog, "[%s][%s, %s, %d]When .ini path is NULL, the struct IniFile should contain the valid path string.\n", 
					tstr, __FILE__, __FUNCTION__, __LINE__);
			fclose(errorLog);
			return -1;
		}
	}

	if(( _pIniFile->pfile = fopen( _pIniFile->aini_path, "r")) == NULL) {
		time_t t=time(NULL);
		char *tstr = asctime(localtime(&t));
		tstr[strlen(tstr)-1] = '\0';
		FILE *errorLog = fopen(ERR_LOG_FNAME, "a+");
		fprintf(errorLog, "[%s][%s, %s, %d]Open .ini file error. Check whether the .ini file(%s) exists.\n", 
				tstr,__FILE__, __FUNCTION__, __LINE__, DEF_INI_PATH);
		fclose(errorLog);
		return -1;
	}


	char key[MAX_CFG_KEYLEN];
	char val[MAX_CFG_VALLEN];
	char line[MAX_CFG_LINE];
	char *c;

	bzero(key, MAX_CFG_KEYLEN);
	bzero(val, MAX_CFG_VALLEN);
	bzero(line, MAX_CFG_LINE);

	while( fgets( line, MAX_CFG_LINE, _pIniFile->pfile ) != NULL) {

		int isKey	= 1;
		int kcount	= 0;
		int vcount	= 0;

		for(c = line; (*c)!='\0'; ++c) {
			if( *c == '#' && kcount == 0) {
				break;
			} else if( isspace(*c) && ((isKey == 1) || (isKey == 0 && vcount == 0))) {
				continue;
			} else if(*c == '=') {
				key[kcount] = '\0';
				isKey = 0;
				continue;
			} else {
				if(isKey) {
					key[kcount++] = *c;
				} else {
					val[vcount++] = *c;
				}
			}
		}

		// Strip the spaces at the end of value.
		while(isspace(val[--vcount]));
		val[vcount+1] = '\0';

		if(strlen(key) == 0 || strlen(val) == 0) {
			continue;
		}

		struct IniPair *pIniPair = &(_pIniFile->aini_pair[ _pIniFile->count++]);

		memcpy(pIniPair->akey, key, strlen(key)+1);
		memcpy(pIniPair->avalue, val, strlen(val)+1);


		bzero(key, MAX_CFG_KEYLEN);
		bzero(val, MAX_CFG_VALLEN);
		bzero(line, MAX_CFG_LINE);

	}

	if(fclose(_pIniFile->pfile) < 0) {

		time_t t=time(NULL);
		char *tstr = asctime(localtime(&t));
		tstr[strlen(tstr)-1] = '\0';
		FILE *errorLog = fopen(ERR_LOG_FNAME, "a+");
		fprintf(errorLog, "[%s][%s, %s, line: %d]Fail to close IniFile.pFile.\n", tstr, __FILE__, __FUNCTION__, __LINE__);

		/* We don't care whether the error.log file is closed correctly */
		fclose(errorLog);
	}

	return _pIniFile->count;

}


/**
 * @brief Query the value according to key that was passed.
 *
 * @param _pIniFile Pointer to the IniFile structure.
 * @param _pKey Pointer to key string.
 * @param _pValue Pointer to the buffer for the coming value.
 * @param _size The size of value buffer.
 *
 * @return Pointer to the value string.
 */
char* api_queryIni(struct IniFile * _pIniFile, const char *_pKey, char *_pValue, unsigned long _size) {

	if(_pIniFile == NULL || _pKey == NULL) {

		time_t t=time(NULL);
		char *tstr = asctime(localtime(&t));
		tstr[strlen(tstr)-1] = '\0';
		FILE *errorLog = fopen(ERR_LOG_FNAME, "a+");
		fprintf(errorLog, "[%s][%s, %s, line: %d]api_queryIni should not have any NULL parameter.\n", tstr, __FILE__, __FUNCTION__, __LINE__);
		fclose(errorLog);
		return NULL;
	}
	int i=0;
	for(i=0; i<_pIniFile->count; ++i) {
		if(strcmp(_pIniFile->aini_pair[i].akey, _pKey) == 0) {
			if( _pValue != (char*)NULL && _size > 1) {
				if(strlen(_pIniFile->aini_pair[i].avalue) >= _size) {
					memcpy( _pValue, _pIniFile->aini_pair[i].avalue, _size-1);
					_pValue[ _size-1] = '\0';
				} else {
					memcpy( _pValue, _pIniFile->aini_pair[i].avalue, strlen(_pIniFile->aini_pair[i].avalue)+1);
				}
			}

			return _pIniFile->aini_pair[i].avalue;
		}
	}

	return (char *)NULL;
}



/**
 * @brief structure holding the IniFile.
 */
typedef union LogFile {
	FILE *pfile;
	int fd;
	/**
	 * Originally, I want to use locking here. But when I realized that
	 * efficieny and speed are much more important than the graceful
	 * logging content, I gave up this idea.
	 */
} Log;



/**
 * @brief Logging support api.
 *
 * @param _pLogFile struct LogFile pointer, encapsulate the FILE pointer, and support future extension.
 * @param _pFILE GCC __FILE__
 * @param _pFUNCTION GCC __FUNCTION__
 * @param _LINE GCC __LINE__
 * @param _pMessage message to log, defined by user
 * @param _pTIME GCC __TIME__
 * @param _pDATE GCC __DATE__
 *
 * @return The number of character that have been logger.
 */
int api_log(union LogFile *_pLogFile, const char *_pFILE, const char *_pFUNCTION, const int _LINE,
		const char *_pMessage, const char *_pTIME, const char *_pDATE)
{

	FILE *pFile;
	int isOpened = 0;

	/* If the first condition is true, the second condition will not be tested.
	 * If the second condition is tested after the first was proved true, it leads to a segment fault.
	 */
	if( _pLogFile == NULL ) {
		char buf[MAX_PATHLEN];
		memcpy(buf, _pFILE, strlen( _pFILE)+1);
		strcat(buf,".log");
		pFile = fopen(buf, "a+");

		if(pFile == NULL) {
			return -1;
		}

		isOpened = 1;

	} else if(_pLogFile->pfile == NULL) {
		char buf[MAX_PATHLEN];
		memcpy(buf, _pFILE, strlen( _pFILE)+1);
		strcat(buf,".log");
		pFile = fopen(buf, "a+");

		if(pFile == NULL) {
			return -1;
		}

		isOpened = 1;
	} else {
		pFile = _pLogFile->pfile;
	}

	time_t t=time(NULL);
	char *tstr = asctime(localtime(&t));
	tstr[strlen(tstr)-1] = '\0';
	int n = fprintf(pFile, "[%s][%s, %s(), line: %d]%s------(COMPLIE TIME: %s, %s)\n",
		tstr, _pFILE, _pFUNCTION, _LINE, _pMessage, _pTIME, _pDATE);
	if( n < 0) {
		return -1;
	}
	/* If the file is opened by api_log, we need to close it. Otherwise, just leave it. */
	if(isOpened) {
		/* We don't care if the file is really closed. */
		fclose(pFile);
	}

	return n;

}

int api_err_log(const char *_pFile, const char *_pFunc, const int line, const char *_pMessage, const char *_pTime, const char *_pDate) {
	int n=0;
	Log log;
	log.pfile = fopen(ERR_LOG_FNAME, "a+");
	n = api_log(&log, _pFile, _pFunc, line, _pMessage, _pTime, _pDate);
	fclose(log.pfile);
	return n;
}

#define API_LOG(_pLogFile,_pMessage)	api_log(_pLogFile,__FILE__,__FUNCTION__,__LINE__,_pMessage,__TIME__,__DATE__)
#define API_ERR_LOG(_pMessage)		api_err_log( __FILE__,__FUNCTION__,__LINE__,_pMessage,__TIME__,__DATE__)

/* memory validity test support */

#if defined(__MACH__) && defined(__FreeBSD__) && defined(__NetBSD__) && defined(__OpenBSD__) && defined(__DragonFly__)
#define ERROR_SIGNAL SIGBUS
#else
#define ERROR_SIGNAL SIGSEGV
#endif

#define _MEMBLOCK_SIZE	32


/**
 * @brief Memory block for testing read and write.
 */
struct _MemBlock {
	char _block[_MEMBLOCK_SIZE];
};

static sigjmp_buf badjmpbuf;


/**
 * @brief Signal handler for SIGSEGV
 *
 * @param _signo The raised signal
 */
static void _badmem(int _signo) {
	siglongjmp(badjmpbuf, 1);
}


/**
 * @brief Test whether the memory space starting from _ptr and lengthened to _memLength
 * is valid for readig and writting.
 * [Note] The OS will allocate extra memory despite the size specified by user. Perhaps
 * memory allocation is done in block or OSs do this in favor of efficiency. So that sometimes
 * extra memory other than the allocated one is valid, while other time it is not.
 *
 * @param _ptr Pointer to the memory space.
 * @param _memLength The length of the memory space.
 *
 * @return Return 1 for valid, and 0 for invalid.
 */
int api_isMemoryValid(void *_ptr, long _memLength) {

	struct sigaction sa, osa;
	int ret	= 1;

	sa.sa_handler	= _badmem;
	sa.sa_flags	= 0;
	sigemptyset(&sa.sa_mask);

	/* If access violation occurs, OS will issue the signal and signal handler will catch it.
	 * Hence this gives us opportunity to handler the signal and inform us whether violation
	 * occurs.
	 */
	if(sigaction(ERROR_SIGNAL, &sa, &osa) < 0) {
		return -1;
	}

	if(sigsetjmp(badjmpbuf, 1) == 0) {

		struct _MemBlock *pMB = (struct _MemBlock *)_ptr;
		struct _MemBlock tMB;
		int hi = _memLength/sizeof(struct _MemBlock);
		int re = _memLength%sizeof(struct _MemBlock);

		int n  = 0;

		for(n=0; n<hi; ++n) {
			tMB = *(pMB+n);
			*(pMB+n) = tMB;
		}

		char *pChar = (char *)(pMB+n);
		char tChar;
		for(n=0; n<re; ++n) {
			tChar = *(pChar+n);
			*(pChar+n) = tChar;
		}

	} else {
		ret = 0;
	}

	if(sigaction(ERROR_SIGNAL, &osa, NULL) < 0) {
		return -1;
	}
	return ret;
}


/**
 * @brief Helper function to display message on the screen through error stream.
 *
 * @param _pMsg Message to display.
 * @param _pFile Source file name.
 * @param _pFunc Calling function name.
 * @param _line Calling line in the source code.
 */
void err_stdout(const char *_pMsg, const char *_pFile, const char *_pFunc, const int _line) {
	
	time_t t = time(0);
	char *asct = asctime(localtime(&t));
	int asclen = strlen(asct);
	asct[asclen-1] = '\0';
	printf("[%s][%s, %s(), line %d]%s\n", asct, _pFile, _pFunc, _line, _pMsg);
}


#define API_ERR_STDOUT(_msg) err_stdout(_msg,__FILE__,__FUNCTION__,__LINE__)



/**
 * @brief Helper method to test whether a file exists.
 *
 * @param _pFilePath File path to test.
 *
 * @return 1 for existence and 0 for absence.
 */
inline int api_isFileExist(const char *_pFilePath) {
	FILE *pFile;
	
	pFile = fopen( _pFilePath, "r");
	if(pFile == NULL) {
		return (int)0;
	} else {
		fclose(pFile);
		return (int)1;
	}
}


/**
 * @brief Wrapper for dlopen().
 *
 * @param _pSoPath Path to so file.
 *
 * @return Return value is aligned with dlopen().
 */
inline void* api_soLoad(const char *_pSoPath) {
	
	if( _pSoPath != (char *)NULL && api_isFileExist( _pSoPath)) {
		return dlopen(_pSoPath, RTLD_LAZY|RTLD_GLOBAL);
	} else {
		return (void*)NULL;
	}
}



/**
 * @brief Wrapper for dlsym()
 *
 * @param _pSo Pointer to beginning of so memory image.
 * @param _pSymbol String of the desired symbol name.
 *
 * @return Return value is aligned with dlsym().
 */
inline void * api_soSymbol(void *_pSo, const char *_pSymbol) {
	
	if( _pSo!= NULL && _pSymbol != (char *)NULL) {
		return dlsym( _pSo, _pSymbol);
	} else {
		return (void *)NULL;
	}
}


/**
 * @brief Wrapper for dlclose().
 *
 * @param _pHandler Pointer to the beginning of so memory image.
 *
 * @return Return value is aligned with dlclose().
 */
inline int api_soClose( void *_pHandler) {
	return dlclose( _pHandler);
}


/**
 * @brief Wrapper for dlerror().
 *
 * @return Return value is aligned with dlerror().
 */
inline char * api_soError(void) {
	return dlerror();
}



/**
 * @brief A macro for easy invocation of a method in so.
 *
 * @param HD_TYPE The method type, defined as typedef <RETURN_TYPE>(*HD_TYPE)(<ARGS>)
 * @param METHOD String of the method to be called.
 * @param pHANDLER Pointer of void, to the so object.
 *
 * @return Align with the invoked method.
 */
#define API_SOCALL(HD_TYPE,METHOD,pHANDLER,args...)		(*(HD_TYPE)api_soSymbol(pHANDLER,METHOD))(args)



/**
 * @brief Signal handler type, for sa_sigaction in struct sigaction.
 */
typedef void(*SIGACTION)(int,siginfo_t *, void *);

/**
 * @brief Signal handler type, for sa_handler in struct sigaction.
 */
typedef void(*SIGHANDLER)(int);



/**
 * @brief Wrapper to facilitate the registry of signal handler using sigaction().
 *
 * @param _signum signo
 * @param _sig2block The content of mask, the signals to be blocked during execution of the signal handler.
 * @param _flags flags that control the behaviors of the sigaction().
 * @param _sigHandler sa_handler
 * @param _sigAction sa_sigaction
 * @param _osa Old Pointer to structure sigaction.
 *
 * @return Aligned with the sigaction().
 */
int api_sigActionRegister(int _signum, sigset_t _sig2block, int _flags, void *_sigHandler, void *_sigAction, struct sigaction *_osa ) {
	if( _sigHandler == (void *)NULL && _sigAction == (void *)NULL ) {
		return (int)-1;
	}
	if(( _flags & SA_SIGINFO) && ( _sigAction == (void *)NULL)) {
		return (int)-1;
	}
	if((!( _flags & SA_SIGINFO)) && ( _sigHandler == (void *)NULL)) {
		return (int)-1;
	}

	struct sigaction sa;
	sa.sa_handler	= (SIGHANDLER)_sigHandler;
	sa.sa_mask	= _sig2block;
	sa.sa_flags	= _flags;
	sa.sa_sigaction	= (SIGACTION)_sigAction;
	
	return sigaction( _signum, &sa, _osa);
}


void *api_clearMalloc(unsigned long _size) {
	void *_p = malloc(_size);
	bzero( _p, _size);
	return _p;
}
#define MALLOC(_SIZE)	api_clearMalloc(_SIZE)


char * api_stripSpace(char *_pStr) {
	if(_pStr==NULL) 
		return NULL;
	if(strlen(_pStr) == 0)
		return _pStr;
	char *s = _pStr;
	char *p = _pStr;
	char *q = _pStr+strlen(_pStr)-1;
	while(isspace(*p)) ++p;
	while(isspace(*q)) --q;
	while(p!=q) { 
		*s++ = *p++;
	}
	*s = *p;
	*++s = '\0';

	return _pStr;
}

char* api_getCharLine(char*_pStr, char *_pBuf, unsigned long  _size) {

	if( _size<=0) {
		return (char *)NULL;
	}
	int n=0;
	int cursor = -1;
	while( _pStr[++cursor] != '\0') {
		if( _pStr[cursor] == '\n') {
			if(n == 0) {
				continue;
			} else {
				_pBuf[n] = '\0';
				return _pStr+cursor+1;
			}
		} else {
			_pBuf[n++] = _pStr[cursor];
			if( _size == n) {
				_pBuf[_size-1] = '\0';
				return _pStr+cursor+1;
			}
		}

	}

	_pBuf[n] = '\0';
	return _pStr+cursor;
}

/**
 * @brief Decide whether the configuration means "on" or "yes".
 *
 * @param _pSwith Swith string deciding a feature is on or off.
 *
 * @return 1 for "yes" and 0 for "no". 
 */
int api_isYes(const char *_pSwith) {
	if(strcmp("YES", _pSwith) == 0) 
		return 1;
	else if(strcmp("Yes", _pSwith) == 0)
		return 1;
	else if(strcmp("yes", _pSwith) == 0)
		return 1;
	else if(strcmp("ON", _pSwith) == 0)
		return 1;
	else if(strcmp("On", _pSwith) == 0)
		return 1;
	else if(strcmp("ON", _pSwith) == 0)
		return 1;
	else if(strcmp("1", _pSwith) == 0) 
		return 1;
	else if(strcmp("N", _pSwith) == 0)
		return 1;
	else if(strcmp("n", _pSwith) == 0)
		return 1;
	else
		return 0;
}

#define DEF_DELIMITER		"=|:,.-\t\n "

inline int api_isDelimiter(const char _c, const char *_pDeli) {
	if(_pDeli == NULL)
		_pDeli=DEF_DELIMITER;
	do {
		if(*_pDeli == _c)
			return (int)1;
	}while(*++_pDeli);

	return (int)0;
}



/**
 * @brief Type of exiting handler used by atexit().
 *
 */
typedef void(*EXIT_HANDLER)(void);



#ifdef __cplusplus
}
#endif



#endif /* SUTIL_H_ */
